<template>
  <div class="my-range-slider">
    <div class="mrs-runway" @dblclick.stop="onAddPoint">
      <div
        v-for="(u, x) in info"
        :key="u.code"
        :style="{ width: u.percent, left: u.startPos }"
        class="mrs-li"
        @dblclick.stop
      >
        <el-tooltip ref="T1" :content="u.startValue" placement="top">
          <div
            class="mrs-btn l"
            @dblclick="onDeletePoint(x, true)"
            @mousedown.stop="onDragStart($event, x, 'T1')"
          />
        </el-tooltip>
        <div class="mrs-bar" @mousedown.stop="onDragStart($event, x)" />
        <el-tooltip ref="T2" :content="u.endValue" placement="top">
          <div
            class="mrs-btn r"
            @mousedown.stop="onDragStart($event, x, 'T2')"
            @dblclick="onDeletePoint(x)" />
        </el-tooltip>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'MultiRange',
  model: {
    prop: 'value',
    event: 'input'
  },
  props: {
    // 4~118;130;162~375
    value: { type: String, required: true },
    min: { type: Number, default: 0 },
    max: { type: Number, default: 300 },
    rangeSign: { type: String, default: '~' }, // 范围标识
    cutSign: { type: String, default: ';|；' } // 段分割标识
  },
  data: () => ({
    isDraging: false,
    info: []
  }),
  watch: {
    value: {
      immediate: true,
      handler(n) {
        try {
          const str = (n || '').replace(/\s/g, '')
          const array = str ? str.split(new RegExp(this.cutSign)) : ['0']
          const reg = new RegExp(this.rangeSign)
          this.info = array.map((u) => {
            const [v1, v2] = reg.test(u) ? u.split(reg) : [u, u]
            if (isNaN(v1)) {
              const errorsign = u.replace(/\d+|;/g, '').substr(0, 1)
              const obj = { message: `当前使用分隔符的是${errorsign}, 请属性rangeSign加上="${errorsign}"` }
              throw obj
            }
            // 防止值超过范围
            const v3 = Math.max(+v1, this.min)
            const v4 = Math.min(typeof v2 === 'undefined' ? +v1 : +v2, this.max)
            // 防止 输入大到小
            const v5 = Math.min(v3, v4)
            const v6 = Math.max(v3, v4)
            return {
              code: v5 + '==' + v6,
              percent: this.onValueToPercent(v6 - v5),
              startPos: this.onValueToPercent(v5 - this.min),
              startValue: String(v5),
              endValue: String(v6)
            }
          })
          this.$nextTick(this.onEmit)
        } catch (e) {
          new Error(e.message)
        }
      }
    }
  },
  methods: {
    // 获取移动后的 百分比
    onValueToPercent(v) {
      const maxValue = this.max - this.min
      return `${(((v) / maxValue) * 100).toFixed(2)}%`
    },
    // 获取移动后的 值
    onMoveValue(initValue, pageX) {
      const maxValue = this.max - this.min
      let v = Math.max(this.min, initValue + (maxValue * pageX) / 100)
      v = Math.min(this.max, v)
      return String(+v.toFixed(0))
    },
    // 鼠标按下
    onDragStart(e, initIndex, initTRef) {
      this.isDraging = false
      window.addEventListener('mousemove', this.onDragging)
      window.addEventListener('mouseup', this.onDragEnd)
      window.addEventListener('mouseleave', this.onDragEnd)
      const row = this.info[initIndex]
      const initBarLeftDistance = this.onOffsetLeft(e.target) - this.onOffsetLeft(this.$el)
      this.downStart = {
        initIndex,
        initPercent: +row.percent.replace('%', ''),
        initStartPos: +row.startPos.replace('%', ''),
        initStartValue: +row.startValue,
        initEndtValue: +row.endValue,
        initPageX: e.pageX,
        initTRef, // tooltip的ref
        initIsLeft: initTRef === 'T1', // 左边按钮
        initIsRight: initTRef === 'T2', // 右边按钮
        initIsCenter: !initTRef, // 中间的bar
        // 移动bar时 限制 左边可移动
        initBarLeftDistance,
        // 移动bar时 限制 右边可移动
        initBarRightDistance: this.$el.offsetWidth - initBarLeftDistance - e.target.offsetWidth
      }
    },
    // 鼠标移动
    onDragging(e) {
      this.isDraging = true
      const { initIndex, initPercent, initStartPos, initStartValue, initEndtValue, initPageX, initTRef, initIsLeft, initIsRight, initBarLeftDistance, initBarRightDistance } = this.downStart
      // pos当前移动百分比  不是像素
      let pos = +((e.pageX - initPageX) / this.$el.offsetWidth * 100).toFixed(2)
      const row = this.info[initIndex]
      if (initIsLeft) { // 左边按钮操作
        if (pos <= initPercent) { // 不超过当前段的右边
          pos = initStartPos + pos <= 0 ? -initStartPos : pos
          this.$set(row, 'percent', `${initPercent - pos}%`)
          this.$set(row, 'startPos', `${initStartPos + pos}%`)
          this.$set(row, 'startValue', this.onMoveValue(initStartValue, pos))
        } else {
          pos = initStartPos + pos >= 100 ? 100 - initStartPos : pos
          this.$set(row, 'percent', `${pos - initPercent}%`)
          this.$set(row, 'startPos', `${initStartPos + initPercent}%`)
          this.$set(row, 'startValue', String(initEndtValue))
          this.$set(row, 'endValue', this.onMoveValue(initEndtValue, pos - initPercent))
        }
      } else if (initIsRight) { // 右边按钮操作
        if (-pos <= initPercent) { // 不超过当前段的左边
          pos = initStartPos + pos + initPercent >= 100 ? 100 - initStartPos - initPercent : pos
          this.$set(row, 'percent', `${initPercent + pos}%`)
          this.$set(row, 'endValue', this.onMoveValue(initEndtValue, pos))
        } else {
          pos = initStartPos + pos + initPercent <= 0 ? -initStartPos - initPercent : pos
          this.$set(row, 'percent', `${-pos - initPercent}%`)
          this.$set(row, 'startPos', `${initStartPos - (-pos - initPercent)}%`)
          this.$set(row, 'startValue', this.onMoveValue(initStartValue, initPercent + pos))
          this.$set(row, 'endValue', String(initStartValue))
        }
      } else { // 中间bar操作
        let newPageX = Math.max(-initBarLeftDistance, e.pageX - initPageX)
        newPageX = Math.min(initBarRightDistance, newPageX)
        pos = +(newPageX / this.$el.offsetWidth * 100).toFixed(2)
        this.$set(row, 'startPos', `${initStartPos + pos}%`)
        this.$set(row, 'startValue', this.onMoveValue(initStartValue, pos))
        this.$set(row, 'endValue', this.onMoveValue(initEndtValue, pos))
      }
      const TRefdom = this.$refs[initTRef]
      initTRef && TRefdom && TRefdom.forEach(u => u.updatePopper())
    },
    // 鼠标按起
    onDragEnd() {
      window.removeEventListener('mousemove', this.onDragging)
      window.removeEventListener('mouseup', this.onDragEnd)
      window.removeEventListener('mouseleave', this.onDragEnd)
      if (this.isDraging) {
        this.info.map((u) => {
          u.code = u.startValue + '==' + u.endValue
          return u
        })
        this.$nextTick(this.onEmit)
      }
      this.isDraging = false
    },
    // 增加点
    onAddPoint(e) {
      window.removeEventListener('mousemove', this.onDragging)
      window.removeEventListener('mouseup', this.onDragEnd)
      window.removeEventListener('mouseleave', this.onDragEnd)
      const outerWidth = this.$el.offsetWidth
      const v = Math.floor(((e.pageX - this.onOffsetLeft(this.$el)) / outerWidth) * (this.max - this.min)) + this.min
      this.info.push({
        code: v + '==' + v,
        percent: '0%',
        startPos: this.onValueToPercent(v),
        startValue: String(v),
        endValue: String(v)
      })
      this.$nextTick(this.onEmit)
    },
    // 删除点
    onDeletePoint(index, isLeft) {
      const row = this.info[index]
      const { startValue: S, endValue: E, startPos, percent } = row
      if (+S !== +E) {
        this.$set(row, 'code', !isLeft ? S + '==' + S : E + '==' + E)
        this.$set(row, 'percent', '0%')
        this.$set(row, 'startPos', !isLeft ? startPos : `${+startPos.replace('%', '') + +percent.replace('%', '')}%`)
        this.$set(row, 'startValue', !isLeft ? S : E)
        this.$set(row, 'endValue', !isLeft ? S : E)
      } else {
        this.info.splice(index, 1) // ranges是一个点 删除
      }
      this.$nextTick(this.onEmit)
    },
    // 提交更新组件
    onEmit() {
      let currentMax = [-Infinity, -Infinity]
      const result = []
      const array = this.info.sort((a, b) =>
        +a.startValue > +b.startValue ? 1 : -1
      )
      array.forEach((u) => {
        const [v1, v2] = [+u.startValue, +u.endValue]
        const M = currentMax[1]
        const splitRangeSign = this.rangeSign.substr(0, 1)
        if (M < v1) {
          result.push(v1 === v2 ? v1.toString() : v1 + splitRangeSign + v2)
          currentMax = [v1, v2]
        } else if (M >= v1 && M < v2) {
          result[result.length - 1] = currentMax[0] + splitRangeSign + v2
          currentMax = [+currentMax[0], v2]
        } else {
          //
        }
      })
      const splitCutSign = this.cutSign.substr(0, 1)
      const string = result.join(splitCutSign)
      this.$emit('input', string)
      this.$emit('change', string)
    },
    // 距离最左边的距离
    onOffsetLeft(dom, distance) {
      distance = distance || 0
      if (['BODY', null].includes(dom.offsetParent.nodeName)) {
        distance += dom.offsetLeft
        return distance
      } else {
        distance += dom.offsetLeft
        return this.onOffsetLeft(dom.offsetParent, distance)
      }
    }
  }
}
</script>

<style lang="scss">

.my-range-slider {
  position: relative;
  padding: 7px 0;
  margin-bottom: 100px;
  user-select: none;
  .mrs-runway {
    height: 6px;
    background-color: #e4e7ed;
    border-radius: 3px;
    cursor: pointer;
  }
  .mrs-li {
    position: absolute;
    top: 0;
    left: 0;
    display: flex;
    align-items: center;
    height: 100%;
    width: 100%;
  }
  .mrs-bar {
    height: 6px;
    background-color: #409eff;
    position: absolute;
    border-radius: 3px;
    top: 7px;
    left: 0;
    right: 0;
  }
  .mrs-btn {
    position: absolute;
    top: 0;
    width: 15px;
    height: 15px;
    border: 2px solid #409eff;
    background-color: #fff;
    border-radius: 15px;
    transition: 0.2s;
    transition: transform 0.3s;
    z-index: 5;
    &.l {
      left: 0;
      margin-left: -10px;
    }
    &.r {
      right: 0;
      margin-right: -10px;
    }

    &:hover {
      transform: scale(1.2);
    }
  }
}
</style>

